#include "object.h"
#include <cmath>

/****************************************************//**
 * Project-Namespace
 ********************************************************/
namespace jae
{
/****************************************************//**
 * Constructor of the class.
 ********************************************************/
Object::Object()
{
    _pTextureManager = NULL;
    _visibility = true;
    _visibilityNormals = false;
    _hasDisplayList = false;
    _displayListIndex = 0;
    this->setPosition(0.0f, 0.0f, 0.0f);
    this->setOrientationBack();
    
    _scaleFactor = 1.0f;
}
/****************************************************//**
 * Destructor of the class.
 ********************************************************/
Object::~Object()
{
    if (_hasDisplayList)
        {
            deleteDisplayList();
        }
}


/****************************************************//**
 * Rotate around the x-axis. (right)
 ********************************************************/
void Object::pitch (GLfloat aAngle)
{
    //Rotate _forwardVector around the right vector:
    _forwardVector = vectorNormalize(_forwardVector * cos(aAngle * PI_DIV_180)
                                      + _upVector * sin(aAngle * PI_DIV_180));

    //get _upVector by crossProduct
    _upVector = vectorCrossProduct(&_forwardVector, &_leftVector);
}

/****************************************************//**
 * Rotate around the y-axis. (up)
 ********************************************************/
void Object::yaw (GLfloat aAngle)
{
    //Rotate _forwardVector around the up vector:
    _leftVector = vectorNormalize(_leftVector * cos(aAngle * PI_DIV_180)
                                      + _forwardVector * sin(aAngle * PI_DIV_180));

    //get _upVector by crossProduct
    _forwardVector = vectorCrossProduct(&_leftVector, &_upVector);
}

/****************************************************//**
 * Rotate around the z-axis. (forward/backward)
 ********************************************************/
void Object::roll (GLfloat aAngle)
{
    //Rotate _forwardVector around the up vector:
    _upVector = vectorNormalize(_upVector * cos(aAngle * PI_DIV_180)
                                    + _leftVector * sin(aAngle * PI_DIV_180));

    //get _upVector by crossProduct
    _leftVector = vectorCrossProduct(&_upVector, &_forwardVector);
}

/****************************************************//**
 * Moves the node upward.
 ********************************************************/
void Object::moveUpward(GLfloat aDistance)
{
    _positionVector = _positionVector + (_upVector * aDistance);
}

/****************************************************//**
 * Moves the node downward. \n
 * See also moveUp()
 ********************************************************/
void Object::moveDownward(GLfloat aDistance)
{
    this->moveUpward(-aDistance);
}

/****************************************************//**
 * Moves the node forward.
 ********************************************************/
void Object::moveForward(GLfloat aDistance)
{
    _positionVector = _positionVector + (_forwardVector * aDistance);
}

/****************************************************//**
 * Moves the node backward. \n
 * See also moveForward()
 ********************************************************/
void Object::moveBackward(GLfloat aDistance)
{
    this->moveForward(-aDistance);
}

/****************************************************//**
 * Moves the node sideward (left). \n
 ********************************************************/
void Object::moveLeft(GLfloat aDistance)
{
    _positionVector = _positionVector + (_leftVector * aDistance);
}

/****************************************************//**
 * Moves the node sideward (right). \n
 * See also moveLeft()
 ********************************************************/
void Object::moveRight(GLfloat aDistance)
{
    this->moveLeft(-aDistance);
}

/****************************************************//**
 * Sets the position of the node.
 ********************************************************/
void Object::setPosition(GLfloat aX, GLfloat aY, GLfloat aZ)
{
    _positionVector = vectorCreate3D(aX, aY, aZ);
}

/****************************************************//**
 * Sets the default orientation. (same as rootnode)
 ********************************************************/
void Object::setOrientationBack()
{
    _forwardVector = vectorCreate3D(0.0f, 0.0f, 1.0f);
    _upVector = vectorCreate3D(0.0f, 1.0f, 0.0f);
    _leftVector = vectorCreate3D(1.0f, 0.0f, 0.0f);
}

/****************************************************//**
 * Returns the positionvector of the node.
 * @return Positionvector of the node
 ********************************************************/
Vector3D& Object::getPositionVector()
{
    return _positionVector;
}

/****************************************************//**
 * Returns the upvector of the node.
 * @return Upvector of the node
 ********************************************************/
Vector3D& Object::getUpVector()
{
    return _upVector;
}

/****************************************************//**
 * Returns the leftvector of the node.
 * @return Leftvector of the node
 ********************************************************/
Vector3D& Object::getLeftVector()
{
    return _leftVector;
}

/****************************************************//**
 * Returns the forwardvector of the node.
 * @return Forwardvector of the node
 ********************************************************/
Vector3D& Object::getForwardVector()
{
    return _forwardVector;
}

/****************************************************//**
 * Moves the node into an direction.
 ********************************************************/
void Object::move (Vector3D aDirection)
{
    _positionVector = _positionVector + aDirection;
}

/****************************************************//**
 * Sets the visibility to show or hide an object.
 ********************************************************/
void Object::setVisibility(bool aVisibility)
{
    _visibility = aVisibility;
}

/****************************************************//**
 * If it is supported by the implementation of the object,
 * the normelvectors of the object will be rendered.\n
 * (e.g. "objectMS3D")
 ********************************************************/
void Object::setVisibilityNormals(bool aVisibilityNormals)
{
    _visibilityNormals = aVisibilityNormals;
}

/****************************************************//**
 * Optimized version of gluLookAt(...).
 * This method is used to set Object(/Node) position and
 * need to be followed by "renderEnd()".
 * @para aCamera relative position to the rootnode
 ********************************************************/
inline void Object::renderBegin()
{   
    glPushMatrix(); //push the current matrix to stack
    checkGLErrors("void Object::renderBegin()"); //TODO To slow??

    glTranslated(_positionVector.x, _positionVector.y, _positionVector.z);

    //transposed rotation-matrix
    GLfloat mNode[4][4] = {{   _leftVector.x,    _leftVector.y,    _leftVector.z, 0.0f}
                          ,{     _upVector.x,      _upVector.y,      _upVector.z, 0.0f}
                          ,{_forwardVector.x, _forwardVector.y, _forwardVector.z, 0.0f}
                          ,{            0.0f,             0.0f,             0.0f, 1.0f}};   

    glMultMatrixf(&mNode[0][0]);
}

/****************************************************//**
 * Sets the Cameraposition back.
 ********************************************************/
inline void Object::renderEnd()
{
    glPopMatrix(); //pop the current matrix from stack
    checkGLErrors("void Object::renderEnd()");
}

/****************************************************//**
 * This Method renders the object (or its object list)
 ********************************************************/
void Object::render()
{
    //Draws a simple camera
    if (_visibility)
    {
        this->renderBegin();

        glEnable(GL_LIGHTING); //TODO warum muss ich das hier einschalten?? brauch ich eigentlich gar nicht!!
        
        if (_hasDisplayList)
        {
            glCallList(_displayListIndex);
        }
        else
            renderModel();
        
        this->renderEnd();
    }
}

/****************************************************//**
 * This an abstract method is called by the render() function 
 * and must be implemented by an inheriting object. 
 ********************************************************/
void Object::renderModel()
{
   //Nothing to render
}

/****************************************************//**
 * Tells the object to use this TextureManager for loading
 * and rendering textures. This method is called automaticly 
 * by SceneManager->createObject(...).
 * ('aTextureManager' should be the TextureManager of the scene!) 
 ********************************************************/
void Object::setTextureManager(TextureManager* aTextureManager)
{
    if(aTextureManager == NULL)
    {
        Logger logger;
        logger.error("WARNING", "void Object::setTextureManager(TextureManager* aTextureManager)",
                "The TextureManager of an object shouldn't be 'NULL'!");
    }
    _pTextureManager = aTextureManager;
}

/****************************************************//**
 * Creates a Displaylist for faster OpenGl rendering. 
 ********************************************************/
void Object::createDisplayList()
{
    if (_hasDisplayList) //Darf nur einmal gerendert werden
    {
        Logger logger;
        logger.error("WARNING", "void Object::createDisplayList()",
                "You can create an OpenGl displaylist just once!");
        return;
    }

    //Generiere DisplayList
    _displayListIndex = glGenLists(1);

    if (_displayListIndex == 0) //DisplayList konnte nicht erstellt werden
    {
        Logger logger;
        logger.error("ERROR", "void Object::createDisplayList()",
                "Can't create a new displaylist! (Out of memory?)");
        return;
    }

    //Render Displayliste
    glNewList( _displayListIndex, GL_COMPILE );
    {
        renderModel();
    }
    glEndList();

    _hasDisplayList = true;
}

/****************************************************//**
 * Frees the memory and deletes the displaylist. (This method
 * must be called by the destruktor!!!!)
 ********************************************************/
void Object::deleteDisplayList()
{
    if (_hasDisplayList)
    {
        glDeleteLists(_displayListIndex, 1);
        _hasDisplayList = false;
    }
    else
    {
        Logger logger;
        logger.error("ERROR", "void ObjectMS3D::DeleteDisplayList(void)",
                "Can't delete displaylist, because object has no display list!");
    }
}

/****************************************************//**
 * Returns true if createDisplaylist() has been called 
 * before.
 ********************************************************/
bool Object::hasDisplayList()
{
    return _hasDisplayList;
}

/****************************************************//**
 * Connects an OpenGl display list with this object.
 ********************************************************/
void Object::setDisplayList(GLuint aDisplayListIndex)
{
    if (_hasDisplayList)
        deleteDisplayList();

    _displayListIndex = aDisplayListIndex;
    _hasDisplayList = true;
}

/****************************************************//**
 * Returns the OpenGl displaylist index.
 ********************************************************/
GLuint Object::getDisplayList()
{
    return _displayListIndex;
}

/****************************************************//**
 * Sets the scale factor of an Object (=Size).
 ********************************************************/
void Object::setScaleFactor(GLfloat aValue)
{
    Logger logger;
    logger.error("ERROR", 
            "void ObjectDL::setScaleFactor(GLfloat aValue)",
            "This method is not implemented for this Type of Object!");
    logger.write("  aValue: "); logger.write(aValue); logger.write("\n");
}

/****************************************************//**
 * @return Returns the scale factor of an Object (=Size).
 ********************************************************/
GLfloat Object::getScaleFactor()
{
    return 1.0f;
}

} //namespace
